/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Effects Extension:	Various "glitzy" effect (powered mostly by the Gimme Animation Module) that can be performed on Gimme objects.
*
*	Requires:
*	fetch.js
*	gimme.query.js
*	gimme.core.js
*
*/

Gimme.Effects =
{
	Animation: function(config)
	{
		var _elements = config.elements || [];
		var _duration = config.duration || 1;
		
		var _from = config.from || {};
		var _to = config.to || {};
		var _delta = {};
		(function initToAndFromValues()
		{
			var i, key, elem,
			f = {},
			t = {},
			d = {};
			
			for (key in _from)
			{
				f[key] = [];
				t[key] = [];
				d[key] = [];
				
				i = _elements.length;
				while (i--)
				{
					elem = _elements[i];
					f[key][i] = typeof _from[key] === 'function' ? _from[key](elem, key) : _from[key];
					t[key][i] = typeof _to[key] === 'function' ? _to[key](elem, key) : _to[key];
					d[key][i] = t[key][i] - f[key][i];
				}
			}
			
			_from = f;
			_to = t;
			_delta = d;
		})();
		
		var _animate = config.animate;
		var _easing = config.easing || function(pct)
		{
			return pct;
		};
		
		this.startTime = config.startTime || 0;
		this.duration = _duration;
		
		this.add_element = function(element)
		{
			_elements.push(element);
		};
		
		this.remove_element = function(element)
		{
			var i = _elements.length;
			while (i--)
			{
				if (_elements[i] === element)
				{
					_elements.splice(i, 1);
					break;
				}
			}		
		};
		
		this.tick = tick;
		function tick(t)
		{	
			_ease = _easing(t);
			
			var i = _elements.length;
			while (i--)
			{
				var key, val = {};
				for (key in _delta)
				{
					val[key] = _from[key][i] + _delta[key][i] * _ease;
				}
				_animate(_elements[i], val, t);				
			}
		}
	},
	
	Storyboard: function(config)
	{
		var _iid;
		var _prevTime;
		var _pct = 0;
		var _dir = 1;
		var _running = false;

		var _animations = config.animations;
		var _duration = config.duration || 0;
		var _completed = config.completed;
		var _aborted = config.aborted;
		
		function start()
		{
			if (!_running)
			{
				_running = true;
				_prevTime = new Date().getTime();
				_iid = setInterval(runner, 1);
			}
		}

		this.begin = begin;
		function begin()
		{
			_pct = 0;
			_dir = 1;
			start();
		}
		
		this.abort = abort;
		function abort()
		{
			pause();
			_aborted && _aborted(_pct);
		}
		
		this.pause = pause;
		function pause()
		{
			clearInterval(_iid);
			_running = false;
			
		};
		
		this.resume = resume;
		function resume()
		{
			start();
		}
		
		this.forward = forward;
		function forward()
		{
			_dir = 1;
			start();
		}
		
		this.backward = backward;
		function backward()
		{
			_dir = -1;
			start();
		}
		
		this.reverse = reverse;
		function reverse()
		{
			_dir *= -1;
			start();
		}
		
		this.seek = seek;
		function seek(pct)
		{
			_pct = pct;
			tick();
		}
		
		this.is_running = isRunning;
		function isRunning()
		{
			return _running;
		}
		
		this.get_direction = getDirection;
		function getDirection()
		{
			return _dir;
		}
		
		this.get_animations = getAnimations;
		function getAnimations()
		{
			return _animations;
		}
		
		this.get_duration = getDuration;
		function getDuration()
		{
			return _duration;
		}
		
		function runner()
		{
			var currentTime = new Date().getTime();
			_pct += (currentTime - _prevTime) / _duration * _dir;
			_prevTime = currentTime;
			_pct = _pct < 0 ? 0 : _pct > 1 ? 1 : _pct;
			
			tick();
			
			if ((_dir === -1 && _pct === 0) || (_dir === 1 && _pct === 1))
			{
				pause();
				_completed && _completed(_pct);
			}
		}
		
		function tick()
		{
			var i = _animations.length;
			while (i--)
			{
				var t = (_duration * _pct - _animations[i].startTime) / _animations[i].duration;
				t = _dir === -1 && t < 0 ? 0 : _dir === 1 && t > 1 ? 1 : t;
				if (t >= 0 && t <= 1)
				{
					_animations[i].tick(t);
				}
			}
		}
	},
	
	Easing:
	{
		Back: new function()
		{
			function easeIn(t, frequency, suppression, amplitude)
			{
				frequency = .5;				// todo: || default value
				suppression = 0;			// todo: || default value
				amplitude = .5;				// todo: || default value

				return Math.pow((1 - t), suppression) * amplitude * Math.sin(2 * Math.PI * t * frequency) * -1 + t;
			}
			this.ease_in = easeIn;
			
			function easeOut(t, frequency, suppression, amplitude)
			{
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, frequency, suppression, amplitude)
			{
			}
			this.ease_in_out = easeInOut;
		},
		
		Bounce: new function()
		{
			function easeIn(t, weightiness, bounces)
			{
				weightiness = weightiness || 1.5;
				bounces = bounces || 4;
				
				return Math.abs(Math.pow(t, weightiness) * Math.cos(Math.PI * t * bounces));
			}
			this.ease_in = easeIn;
			
			function easeOut(t, weightiness, bounces)
			{
				return 1 - Gimme.Effects.Easing.Bounce.easeIn(1 - t, weightiness, bounces);
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, weightiness, bounces)
			{
				var delta = 1;
				
				var adj = 0;
				var ease = Gimme.Effects.Easing.Bounce.easeIn;
				if (t > .5)
				{
					ease = Gimme.Effects.Easing.Bounce.easeOut;
					t -= .5;
					adj = delta / 2;
				}
				
				return ease(t, weightiness, bounces) - adj;
			}
			this.ease_in_out = easeInOut;
		},

		Elastic: new function()
		{
			function easeIn(t, springiness, oscillations)
			{
			}
			this.ease_in = easeIn;
			
			function easeOut(t, springiness, oscillations)
			{
				springiness = springiness || 3.5;
				oscillations = oscillations || 5;
				
				return 1 - (Math.pow(1 - t, springiness) * Math.cos(2 * Math.PI * t * oscillations));
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, springiness, oscillations)
			{
			}
			this.ease_in_out = easeInOut;
		},
		
		Exponential: new function()
		{
			function easeIn(t, exp)
			{
				return Math.pow(t, exp);
			}
			this.ease_in = easeIn;
			
			function easeOut(t, exp)
			{
				return Math.pow(t, 1 / exp);
			}
			this.ease_out = easeOut;
			
			function easeInOut(t, exp)
			{
				return t <= .5 ? easeIn(t * 2, exp) * .5 : (easeOut((t - .5) * 2, exp) * .5) + .5;
			}
			this.ease_in_out = easeInOut;
		}
	},
	
	Enums:
	{
		Directions:
		{
			vertically: 1,
			horizontally: 2,
			both: 3
		},
		
		Speeds:
		{
			quickly: 500,
			slowly: 1000
		}
	},
	
	Animations:
	{
		fade: function(start, end, duration, guid, callback, easing)
		{
			// IE hack to force "hasLayout"
			this.set_style({ zoom: 1 });
			
			if (start === null)
			{
				start = function(elem, prop)
				{
					return Number(Gimme(elem).get_style(prop));
				};
			}
			
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// items to animate
				this,
				
				//from
				{ opacity: start },
				
				// to
				{ opacity: end },
				
				// animate function
				function(elem, props)
				{
					Gimme(elem).set_style({ opacity: props.opacity });
				},
				
				// duration
				duration,
				
				// guid
				guid,
				
				// callback
				callback,
				
				// easing
				null
			);
			
			sb.begin();
			return this;
		},

		bounce: function(from, to, duration, guid, callback)
		{
			function getTop(elem)
			{
				return Gimme(elem).get_style_pos().y;
			}
			
			if (from === null)
			{
				from = getTop;
			}
			
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// items to animate
				this,
				
				//from
				{ top: from },
				
				// to
				{ top: to },
				
				// animate function
				function(elem, props)
				{
					elem.style.top = Math.ceil(props.top) + 'px';
				},
				
				// duration
				duration,
				
				// guid
				guid,
				
				// callback
				callback,
				
				// easing
				Gimme.Effects.Easing.Bounce.easeOut
			);
			
			sb.begin();			
			return this;
		},
	
		slide: function(fromPt, toPt, duration, guid, callback, easing)
		{
			function getPos(elem, axis)
			{
				axis = axis === 'left' ? 'x' : 'y';
				
				var p = elem.__pos;
				if (!p)
				{
					p = elem.__pos = Gimme(elem).get_style_pos();
				}
				
				if (axis === 'y')
				{
					elem.__pos = Gimme.undefined;
				}
				
				return p[axis];
			}
			
			if (fromPt === null)
			{
				fromPt = { x: getPos, y: getPos };
			}
						
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// items to animate
				this,
				
				// from
				{
					left: fromPt.x,
					top: fromPt.y
				},
				
				//to
				{
					left: toPt.x,
					top: toPt.y
				},				
				
				//animate function
				function(elem, props)
				{
					elem.style.top = Math.round(props.top) + 'px';
					elem.style.left = Math.round(props.left) + 'px';
				},
				
				//durations
				duration,
				
				//guid
				guid,
				
				//callback
				callback,
				
				//easing
				easing || function(t)
				{
					return Gimme.Effects.Easing.Exponential.ease_in_out(t, 4);
				}
			);
			
			sb.begin();
			return this;
		},
		
		scrollTo: function(duration, guid, callback, easing)
		{
			scrollPos = Gimme.Screen.get_scroll_pos();
			elemPos = Gimme(this[0]).get_absolute_pos();
			
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// items to animate
				[ window ],
				
				//from
				{
					scrollX: scrollPos.x,
					scrollY: scrollPos.y
				},
				
				//to
				{
					scrollX: elemPos.x,
					scrollY: elemPos.y
				},
				
				// animate function
				function(win, props)
				{
					win.scrollTo(Math.round(props.scrollX), Math.round(props.scrollY));
				},
				
				//duration
				duration,
				
				//guid
				guid,
				
				//callback,
				callback,
				
				//easing
				easing || function(t)
				{
					return Gimme.Effects.Easing.Exponential.easeOut(t, 4);
				}
			);
			
			sb.begin();
			return this;
		},
		
		veil: function(direction, duration, guid, callback, easing)
		{
			var D = Gimme.Effects.Enums.Directions;
			direction = Math.floor(direction) || D[direction] || D.vertically;
			
			var expando = '_$gimme$_veil';
			
			// Save important values in an expando so they can be used for unveil (if needed):			
			// height, width, paddingTop, paddingBottom, paddingRight, paddingLeft
			function getFrom(elem, prop)
			{
				var v = parseInt(Gimme(elem).get_style(prop), 10);
				elem[expando] = elem[expando] || {};
				return elem[expando][prop] = v;				
			}
			
			var to = {};
			var from = {};
			if ((direction & D.vertically) === D.vertically)
			{
				from.height = from.paddingTop = from.paddingBottom = getFrom;
				to.height = to.paddingTop = to.paddingBottom = 0;
			}
			if ((direction & D.horizontally) === D.horizontally)
			{
				from.width = from.paddingLeft = from.paddingRight = getFrom;
				to.width = to.paddingLeft = to.paddingRight = 0;
			}
			
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// elements to be animated
				this.filter(function(elem)
				{
					var display = elem.style.display || Gimme(elem).get_style('display');
					return display !== 'none';
				}),
				
				// from
				from,
				
				// to
				to,
				
				//animate function
				function(elem, props)
				{
					var elemStyle = elem.style;
					if ((direction & D.vertically) === D.vertically)
					{
						elemStyle.height = props.height + 'px';
						elemStyle.paddingTop = props.paddingTop + 'px';
						elemStyle.paddingBottom = props.paddingBottom + 'px';
					}
					if ((direction & D.horizontally) === D.horizontally)
					{
						elemStyle.width = props.width + 'px';
						elemStyle.paddingRight = props.paddingRight + 'px';
						elemStyle.paddingLeft = props.paddingLeft + 'px';
					}
				},
				
				//duration
				duration,
				
				//guid
				guid,
				
				//callback
				callback,
				
				//easing
				easing || function(t)
				{
					return Gimme.Effects.Easing.Exponential.ease_in_out(t, 4);
				}
			);
			
			// add a discrete animation for setting overflow to hidden
			sb.get_animations().push
			(
				new Gimme.Effects.Animation(
				{
					startTime: 0,
					elements: this,
					animate: function(elem)
					{
						// Make sure content isn't bleeding over its container during the veiling process				
						elem.style.overflow = 'hidden';
					}
				}),
				
				new Gimme.Effects.Animation(
				{
					startTime: sb.get_duration(),
					elements: this,
					animate: function(elem)
					{
						// bye bye
						elem.style.display = 'none';
					}
				})
			);
			
			// start the animation storyboard			
			sb.begin();
			return this;
		},

		unveil: function(direction, duration, guid, callback, easing)
		{
			var D = Gimme.Effects.Enums.Directions;
			direction = Math.floor(direction) || D[direction] || D.vertically;
			
			function getTo(elem, prop)
			{
				var to = elem['_$gimme$_veil'];
				if (!to)
				{
					to = to || (function()
					{
						// clone for measurement purposes
						var clone = elem.cloneNode(true);
						clone.setAttribute('style','position:absolute;top:0;left:0;visibility:hidden;margin:0;padding:0;border:0;height:;width:;');
						clone.style.display = 'block';						
						elem.parentNode.appendChild(clone);

						clone = Gimme(clone)						
						var h = parseInt(Gimme(elem).get_style('height'), 10) || parseInt(clone.get_style('height'), 10);
						var w = parseInt(Gimme(elem).get_style('width'), 10) || parseInt(clone.get_style('width'), 10);
						clone[0].style.padding = elem.style.padding;

						var rv =
						{
							height: h,
							width: w,
							paddingTop: parseInt(clone.get_style('paddingTop'), 10),
							paddingBottom: parseInt(clone.get_style('paddingBottom'), 10),
							paddingRight: parseInt(clone.get_style('paddingRight'), 10),
							paddingLeft: parseInt(clone.get_style('paddingLeft'), 10)
						};
						
						elem.parentNode.removeChild(clone[0]);
						elem.style.overflow = 'hidden';

						return rv;
					})();
				}
				
				return to[prop];
			}
			
			var D = Gimme.Effects.Enums.Directions;
			direction = Math.floor(direction) || D[direction] || D.vertically;

			var to = {};
			var from = {};
			if ((direction & D.vertically) === D.vertically)
			{
				from.height = from.paddingTop = from.paddingBottom = 0;
				to.height = to.paddingTop = to.paddingBottom = getTo;
			}
			if ((direction & D.horizontally) === D.horizontally)
			{
				from.width = from.paddingLeft = from.paddingRight = 0;
				to.width = to.paddingLeft = to.paddingRight = getTo;
			}
			
			var sb = Gimme.Effects.Animations.createStoryboard
			(
				// items to animate
				this.filter(function(elem)
				{
					var display = elem.style.display || Gimme(elem).get_style('display');
					// Safari 2 will return a null CSSStyleDeclaration object if the element is not currently displayed on the screen
					// Thus, the need for the explicit, display === null check
					return display === 'none' || display === null;
				}),
				
				// from
				from,
				
				//to
				to,
				
				//animate function
				function(elem, props)
				{
					if ((direction & D.vertically) === D.vertically)
					{
						elem.style.height = props.height + 'px';
						elem.style.paddingTop = props.paddingTop + 'px';
						elem.style.paddingBottom = props.paddingBottom + 'px';
					}
					if ((direction & D.horizontally) === D.horizontally)
					{
						elem.style.width = props.width + 'px';
						elem.style.paddingRight = props.paddingRight + 'px';
						elem.style.paddingLeft = props.paddingLeft + 'px';
					}
				},
				
				//durations
				duration,
				
				//guid
				guid,
				
				//callback
				callback,
				
				//easing
				easing || function(t)
				{
					return Gimme.Effects.Easing.Exponential.ease_in_out(t, 4);
				}
			);
			
			sb.get_animations().push
			(
				new Gimme.Effects.Animation(
				{
					startTime: 0,
					elements: this,
					animate: function(elem)
					{
						// make sure contents don't bleed over the element container during the unveiling
						elem.style.overflow = 'hidden';
						
						// make sure we can see it
						elem.style.display = 'block';
					}
				}),
				
				new Gimme.Effects.Animation(
				{
					startTime: sb.get_duration(),
					elements: this,
					animate: function(elem)
					{
						// reset the overflow back to normal once the unveil is done
						elem.style.overflow = '';
					}
				})
			);
			
			sb.begin();
			return this;
		},
		
		animate: function(props, duration, guid, callback, easing)
		{
			var from = {units: {}}, to = {};
			for (var key in props)
			{
				from[key] = getFrom;
				to[key] = getTo;
			}

			var sb = Gimme.Effects.Animations.createStoryboard(this, from, to, function(elem, p)
			{
				var gim = Gimme(elem);
				for (var k in p)
				{
					if (k === 'units') continue;
					var d = {};
					d[k] = p[k] + from.units[k];
					gim.set_style(d);
				}
			},
			duration,
			guid,
			callback,			
			easing);

			sb.begin();

			function getFrom(elem, key)
			{
				var v = Gimme(elem).get_style(key);
				from.units[key] = (v + '').match(/[^\d]+$/) || '';
				return parseFloat(v);
			}

			function getTo(elem, key)
			{
				var f = from[key](elem, key);
				var v = eval('f' + props[key]);
				return v;        
			}
		},

		createStoryboard: function(items, from, to, processFn, duration, guid, callback, easing)
		{
			duration = Math.floor(duration) || Gimme.Effects.Enums.Speeds[duration] || Gimme.Effects.Enums.Speeds.quickly;		
			var sb = new Gimme.Effects.Storyboard(
			{			
				duration: duration,
				completed: function()
				{
					callback && callback.call(this);
					Gimme.Effects.RunningAnimations.remove(guid);
					sb = null;
				},
				aborted: function()
				{
					Gimme.Effects.RunningAnimations.remove(guid);
					sb = null;
				},
				animations:
				[
					new Gimme.Effects.Animation(
					{
						from: from,
						to: to,
						duration: duration,
						animate: processFn,
						easing: easing,
						elements: items
					})
				]
			});
			
			if (guid)
			{
				Gimme.Effects.RunningAnimations.add(guid, sb);
			}
			
			return sb;
		}
	},

	RunningAnimations: function()
	{
		var _storyboards = {};
		return {
			add: function(guid, sb)
			{
				_storyboards[guid] = sb;
			},
			
			remove: function(guid)
			{
				delete _storyboards[guid];
			},
			
			get: function(guid)
			{
				return _storyboards[guid] || new Gimme.Effects.Storyboard({animations:[]});
			}
		};		
	}()
};


// fade
Gimme.ext.fade_to = Gimme.Effects.Animations.fade;
Gimme.ext.fade_in = function(duration, guid, callback, easing)
{
	return Gimme.Effects.Animations.fade.call(this, 0, 1, duration, guid, callback, easing);
};
Gimme.ext.fade_out = function(duration, guid, callback, easing)
{
	return Gimme.Effects.Animations.fade.call(this, 1, 0, duration, guid, callback, easing);
};

// bounce
Gimme.ext.bounce = Gimme.Effects.Animations.bounce;

// slide
Gimme.ext.slide = Gimme.Effects.Animations.slide;

// scrollTo
Gimme.ext.scroll_to = Gimme.Effects.Animations.scrollTo;

// veil
Gimme.ext.veil = Gimme.Effects.Animations.veil;

// unveil
Gimme.ext.unveil = Gimme.Effects.Animations.unveil;

// generic animate
Gimme.ext.animate = Gimme.Effects.Animations.animate;

/*
Gimme.ext.followPath = function(_path, _factor, _duration, _guid, _callback)
{
	_factor = _factor || 1;
	g(this.entities).animate(_path, _duration, _guid, _callback, doFollowPath, setup);
	
	function setup(_elem)
	{
		var startPt = g(_elem).getComputedPosition();
		
		return {
		startPt: startPt };
	}
	
	function doFollowPath(_elem, _pctComplete, _path, _args)
	{
		var startPt = _args.startPt;
		
		var p = _path.getPoint(_pctComplete);
		
		var x = startPt.x + p.x * _factor;
		var y = startPt.y + p.y * _factor;
			
		_elem.style.top = Math.floor(Math.round(y)) + 'px';
		_elem.style.left = Math.floor(Math.round(x)) + 'px';
	}
	
	return this;
};
*/
